import json
import os
import threading
import time
import logging
from dataclasses import dataclass
from typing import Dict, Optional
import uuid
from dataclasses_json import DataClassJsonMixin
from shared_types import ROLES, ConnectedMsg, DownHeartbeatMsg
from ..models import WifiDevice, DeviceFunctionalConfig, new_device_config
from ..communication import UDPSender

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)


class DuplicateDeviceNameError(ValueError):
    def __init__(self, device_name: str, existed_ip: str, new_ip: str) -> None:
        self.msg = f"IP地址为{new_ip}的设备，与地址为{
            existed_ip}的设备名称均为{device_name}，请检查是否重名。"
        super().__init__(self.msg)

# 管理设备的配置
# 设备的配置可以手动进行添加或删除，当有同名的Wifi节点上线时，就会自动创建一个；


class DevicesConfigManager:
    class ConfigExistsError(ValueError):
        pass

    def __init__(self, config_path: str) -> None:
        self.config_path = config_path
        self.devices_configs: list[DeviceFunctionalConfig] = []
        self.load_configs()

    def add_config(self, config: DeviceFunctionalConfig) -> None:
        if self.get_config(config['device_name']) is not None:
            raise self.ConfigExistsError(
                f"Device config {config['device_name']} already exists")
        self.devices_configs.append(config)
        self.save_configs()

    def create_config(self, device_name: str, role: ROLES) -> DeviceFunctionalConfig:
        cfg = new_device_config(uuid=uuid.uuid4().hex, device_name=device_name, role=role)
        self.add_config(cfg)
        return cfg

    def get_config(self, controller_name: str) -> Optional[DeviceFunctionalConfig]:
        ret = list(filter(
            lambda cfg: cfg['device_name'] == controller_name,
            self.devices_configs))
        if len(ret) == 0:
            return None
        else:
            return ret[0]

    # load configs from json file

    def load_configs(self):
        if not os.path.exists(self.config_path):
            self.devices_configs = []
            self.save_configs()
            return
        with open(self.config_path, "r", encoding="utf8") as f:
            self.devices_configs = json.load(f)

    # save configs

    def save_configs(self):
        with open(self.config_path, "w") as f:
            json.dump([cfg
                      for cfg in self.devices_configs], f, indent=2)


class DevicesManager:
    _instance = None

    def __init__(self, config_folder: str):
        self.devices: list[WifiDevice] = []
        self.dev_cfg_mgr: DevicesConfigManager = DevicesConfigManager(os.path.join(
            config_folder, "devices_config.json"
        ))
        self.device_last_heartbeat: Dict[str,
                                         float] = {}  # 设备名称 --> 收到设备的UDP心跳包时间

        self.udp_sender = UDPSender(10130)
        self._keep_alive_thread = threading.Thread(
            target=self._keep_alive_task)
        self._keep_alive_thread.daemon = True
        self._keep_alive_thread.start()
        self.__class__._instance = self

    @classmethod
    def get_instance(cls) -> "DevicesManager":
        if cls._instance is None:
            raise TypeError(f"{cls}'s instance is not initialized")
        else:
            return cls._instance

    def add_device(self, device: WifiDevice) -> None:
        if (existed_device := self.get_device_by_name(device.name)) is not None:
            if existed_device.ip != device.ip:
                raise DuplicateDeviceNameError(
                    device.name, existed_device.ip, device.ip)
            else:
                # 如果相同IP和名称的设备已存在，则不需要添加了。
                return
        self.devices.append(device)
        if self.dev_cfg_mgr.get_config(device.name) is None:
            self.dev_cfg_mgr.create_config(device.name, device.role)
        self.update_device_heartbeat_time(device.name)

    def get_device_by_name(self, name: str) -> WifiDevice | None:
        for device in self.devices:
            if device.name == name:
                return device
        return None

    def get_all_devices(self) -> list[WifiDevice]:
        return self.devices

    def update_device_heartbeat_time(self, device_name: str):
        self.device_last_heartbeat[device_name] = time.time()
    
    def detect_and_remove_offline_devices(self):
        """
        检测已经离线的设备，并将其从列表中移除
        
        检测标准：距离收到上一次设备的上行心跳（ETT_HEARTBEAT_UP）8s以上

        注意：设置项无需移除，需要保存在本地的JSON文件中。
        """
        current_time = time.time()
        for device in self.devices:
            print(current_time, self.device_last_heartbeat.get(device.name, 0))
            if current_time - self.device_last_heartbeat.get(device.name, 0) > 8:
                logger.info("设备 {0} 已下线并移除".format(device.name))
                self.devices.remove(device)
                
        time.sleep(1)


    def _keep_alive_task(self):
        """
        周期性运行的保活UDP包发送
        发送给所有已经连接的设备
        """
        while True:
            logger.info(f"sending keep alive msg to {self.devices}")
            for dev in self.devices:
                self.udp_sender.send(
                    dev.ip,
                    json.dumps(DownHeartbeatMsg(type="ETT_HEARTBEAT_DOWN")).encode(
                        "ascii"
                    ),
                )
            time.sleep(3)
            self.detect_and_remove_offline_devices()
